/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3

import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.io.Closeable
import java.io.File
import java.io.Flushable
import java.io.IOException
import java.security.cert.Certificate
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.util.NoSuchElementException
import java.util.TreeSet
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.internal.EMPTY_HEADERS
import okhttp3.internal.cache.CacheRequest
import okhttp3.internal.cache.CacheStrategy
import okhttp3.internal.cache.DiskLruCache
import okhttp3.internal.closeQuietly
import okhttp3.internal.concurrent.TaskRunner
import okhttp3.internal.http.HttpMethod
import okhttp3.internal.http.StatusLine
import okhttp3.internal.io.FileSystem
import okhttp3.internal.platform.Platform
import okhttp3.internal.toLongOrDefault
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
import okio.ByteString.Companion.decodeBase64
import okio.ByteString.Companion.encodeUtf8
import okio.ByteString.Companion.toByteString
import okio.ForwardingSink
import okio.ForwardingSource
import okio.Sink
import okio.Source
import okio.buffer

/**
 * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and
 * bandwidth.
 *
 * ## Cache Optimization
 *
 * To measure cache effectiveness, this class tracks three statistics:
 *
 *  * **[Request Count:][requestCount]** the number of HTTP requests issued since this cache was
 *    created.
 *  * **[Network Count:][networkCount]** the number of those requests that required network use.
 *  * **[Hit Count:][hitCount]** the number of those requests whose responses were served by the
 *    cache.
 *
 * Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of
 * the response, the client will issue a conditional `GET`. The server will then send either
 * the updated response if it has changed, or a short 'not modified' response if the client's copy
 * is still valid. Such responses increment both the network count and hit count.
 *
 * The best way to improve the cache hit rate is by configuring the web server to return cacheable
 * responses. Although this client honors all [HTTP/1.1 (RFC 7234)][rfc_7234] cache headers, it
 * doesn't cache partial responses.
 *
 * ## Force a Network Response
 *
 * In some situations, such as after a user clicks a 'refresh' button, it may be necessary to skip
 * the cache, and fetch data directly from the server. To force a full refresh, add the `no-cache`
 * directive:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder().noCache().build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * If it is only necessary to force a cached response to be validated by the server, use the more
 * efficient `max-age=0` directive instead:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .maxAge(0, TimeUnit.SECONDS)
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * ## Force a Cache Response
 *
 * Sometimes you'll want to show resources if they are available immediately, but not otherwise.
 * This can be used so your application can show *something* while waiting for the latest data to be
 * downloaded. To restrict a request to locally-cached resources, add the `only-if-cached`
 * directive:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .onlyIfCached()
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * Response forceCacheResponse = client.newCall(request).execute();
 * if (forceCacheResponse.code() != 504) {
 *   // The resource was cached! Show it.
 * } else {
 *   // The resource was not cached.
 * }
 * ```
 *
 * This technique works even better in situations where a stale response is better than no response.
 * To permit stale cached responses, use the `max-stale` directive with the maximum staleness in
 * seconds:
 *
 * ```
 * Request request = new Request.Builder()
 *     .cacheControl(new CacheControl.Builder()
 *         .maxStale(365, TimeUnit.DAYS)
 *         .build())
 *     .url("http://publicobject.com/helloworld.txt")
 *     .build();
 * ```
 *
 * The [CacheControl] class can configure request caching directives and parse response caching
 * directives. It even offers convenient constants [CacheControl.FORCE_NETWORK] and
 * [CacheControl.FORCE_CACHE] that address the use cases above.
 *
 * [rfc_7234]: http://tools.ietf.org/html/rfc7234
 */
class Cache internal constructor(
  directory: File,        // 缓存目录
  maxSize: Long,          // 缓存的最大大小
  fileSystem: FileSystem // 文件系统实例
) : Closeable, Flushable {
  internal val cache = DiskLruCache(
    fileSystem = fileSystem,
    directory = directory,
    appVersion = VERSION,   // 应用程序版本
    valueCount = ENTRY_COUNT, // 条目数量
    maxSize = maxSize,       // 最大缓存大小
    taskRunner = TaskRunner.INSTANCE // 任务运行器实例
  )

  // 读取和写入统计数据，均受 'this' 锁保护。
  internal var writeSuccessCount = 0      // 写入成功次数
  internal var writeAbortCount = 0        // 写入中止次数
  private var networkCount = 0           // 网络请求次数
  private var hitCount = 0               // 命中次数
  private var requestCount = 0           // 请求次数

  val isClosed: Boolean
    get() = cache.isClosed() // 判断缓存是否已关闭

  /**
   * 创建一个最大容量为 [maxSize] 字节的缓存，存储在指定的 [directory] 目录中。
   *
   * @param directory 缓存目录
   * @param maxSize 缓存的最大容量（以字节为单位）
   */
  constructor(directory: File, maxSize: Long) : this(directory, maxSize, FileSystem.SYSTEM)

  /**
   * 根据请求 [request] 获取响应，如果缓存中没有匹配的响应，则返回 null。
   *
   * @param request 要获取响应的请求
   * @return 如果有匹配的缓存响应则返回响应，否则返回 null
   */
  /*
  总结获取缓存流程大体是：
第一步 获取key
第一步 获取DiskLruCache.Snapshot对象
第三步 获取Entry对象
第四步 获取response
第五步 检查是response
   */
  internal fun get(request: Request): Response? {
    val key = key(request.url) // 获取url经过MD5和HEX 十六进制编码的key

    //根据key来获取一个snapshot，由此可知我们的key-value里面的value对应的是snapshot
    val snapshot: DiskLruCache.Snapshot = try {
      cache[key] ?: return null
    } catch (_: IOException) {
      return null // 放弃，因为无法读取缓存。
    }


    //利用前面的Snapshot创建一个Entry对象。存储的内容是响应的Http数据包Header部分的数据。
    // snapshot.getSource得到的是一个Source对象 (source是okio里面的一个接口)
    val entry: Entry = try {
      Entry(snapshot.getSource(ENTRY_METADATA))
    } catch (_: IOException) {
      snapshot.closeQuietly()
      return null
    }

    //利用entry和snapshot得到Response对象
    val response = entry.response(snapshot)


    //对request和Response进行比配检查，成功则返回该Response。
    // 匹配方法就是url.equals(request.url().toString()) &&
    // requestMethod.equals(request.method()) &&
    // OkHeaders.varyMatches(response, varyHeaders, request);
    // 其中Entry.url和Entry.requestMethod两个值在构建的时候就被初始化好了，
    // 初始化值从命中的缓存中获取。
    // 因此该匹配方法就是将缓存的请求url和请求方法跟新的客户请求进行对比。
    // 最后OkHeaders.varyMatches(response, varyHeaders, request)是检查命中
    // 的缓存Http报头跟新的客户请求的Http报头中的键值对是否一样。
    // 如果全部结果为真，则返回命中的Response。

    if (!entry.matches(request, response)) {
      response.body?.closeQuietly()
      return null
    }

    return response
  }


  //放入缓存
  internal fun put(response: Response): CacheRequest? {
    val requestMethod = response.request.method
    //判断请求如果是"POST"、"PATCH"、"PUT"、"DELETE"、"MOVE"中的任何一个
    // 则调用DiskLruCache.remove(urlToKey(request));将这个请求从缓存中移除出去。
    if (HttpMethod.invalidatesCache(response.request.method)) {
      try {
        remove(response.request)
      } catch (_: IOException) {
        // The cache cannot be written.
      }
      return null
    }

    //判断请求如果不是Get则不进行缓存，直接返回null。
    if (requestMethod != "GET") {
      // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
      // POST requests, but the complexity of doing so is high and the benefit is low.
      // 不要缓存非GET响应。从技术上讲，我们可以缓存HEAD请求和一些POST请求，但实现它的复杂性很高，而收益很低。
      return null
    }

    //判断请求中的http数据包中headers是否有符号"*"的通配符，有则不缓存直接返回null
    //通配符"*"意味着表示对任何资源的请求，而不是特定的资源，容易导致不确定的行为，所以不缓存
    if (response.hasVaryAll()) {
      return null
    }

    //由Response对象构建一个Entry对象,Entry是Cache的一个内部类
    val entry = Entry(response)
    var editor: DiskLruCache.Editor? = null
    try {
      //通过调用DiskLruCache.edit();方法得到一个DiskLruCache.Editor对象。
      editor = cache.edit(key(response.request.url)) ?: return null
      //把这个entry写入
      //方法内部是通过Okio.buffer(editor.newSink(ENTRY_METADATA));
      // 获取到一个BufferedSink对象，随后将Entry中存储的Http报头数据写入到sink流中。
      entry.writeTo(editor)
      return RealCacheRequest(editor)
    } catch (_: IOException) {
      abortQuietly(editor)
      return null
    }
  }


  //删除方法，key()这个方法原来就说获取url的MD5和hex生成的key
  @Throws(IOException::class)
  internal fun remove(request: Request) {
    cache.remove(key(request.url))
  }


  /**
   * 更新方法的流程如下：
   * 1.首先要获取entry对象
   * 2.获取DiskLruCache.Editor对象
   * 3.写入entry对象
   */
  internal fun update(cached: Response, network: Response) {
    //用response构造一个Entry对象
    val entry = Entry(network)
    //从命中缓存中获取到的DiskLruCache.Snapshot
    val snapshot = (cached.body as CacheResponseBody).snapshot
    //从DiskLruCache.Snapshot获取DiskLruCache.Editor()对象
    var editor: DiskLruCache.Editor? = null
    try {
      editor = snapshot.edit() ?: return // edit() returns null if snapshot is not current.
      //将entry写入editor中
      entry.writeTo(editor)
      editor.commit()
    } catch (_: IOException) {
      abortQuietly(editor)
    }
  }


  private fun abortQuietly(editor: DiskLruCache.Editor?) {
    // Give up because the cache cannot be written.
    try {
      editor?.abort()
    } catch (_: IOException) {
    }
  }

  /**
   * Initialize the cache. This will include reading the journal files from the storage and building
   * up the necessary in-memory cache information.
   *
   * The initialization time may vary depending on the journal file size and the current actual
   * cache size. The application needs to be aware of calling this function during the
   * initialization phase and preferably in a background worker thread.
   *
   * Note that if the application chooses to not call this method to initialize the cache. By
   * default, OkHttp will perform lazy initialization upon the first usage of the cache.
   */
  @Throws(IOException::class)
  fun initialize() {
    cache.initialize()
  }

  /**
   * Closes the cache and deletes all of its stored values. This will delete all files in the cache
   * directory including files that weren't created by the cache.
   */
  @Throws(IOException::class)
  fun delete() {
    cache.delete()
  }

  /**
   * Deletes all values stored in the cache. In-flight writes to the cache will complete normally,
   * but the corresponding responses will not be stored.
   */
  @Throws(IOException::class)
  fun evictAll() {
    cache.evictAll()
  }

  /**
   * Returns an iterator over the URLs in this cache. This iterator doesn't throw
   * `ConcurrentModificationException`, but if new responses are added while iterating, their URLs
   * will not be returned. If existing responses are evicted during iteration, they will be absent
   * (unless they were already returned).
   *
   * The iterator supports [MutableIterator.remove]. Removing a URL from the iterator evicts the
   * corresponding response from the cache. Use this to evict selected responses.
   */
  @Throws(IOException::class)
  fun urls(): MutableIterator<String> {
    return object : MutableIterator<String> {
      private val delegate: MutableIterator<DiskLruCache.Snapshot> = cache.snapshots()
      private var nextUrl: String? = null
      private var canRemove = false

      override fun hasNext(): Boolean {
        if (nextUrl != null) return true

        canRemove = false // Prevent delegate.remove() on the wrong item!
        while (delegate.hasNext()) {
          try {
            delegate.next().use { snapshot ->
              val metadata = snapshot.getSource(ENTRY_METADATA).buffer()
              nextUrl = metadata.readUtf8LineStrict()
              return true
            }
          } catch (_: IOException) {
            // We couldn't read the metadata for this snapshot; possibly because the host filesystem
            // has disappeared! Skip it.
          }
        }

        return false
      }

      override fun next(): String {
        if (!hasNext()) throw NoSuchElementException()
        val result = nextUrl!!
        nextUrl = null
        canRemove = true
        return result
      }

      override fun remove() {
        check(canRemove) { "remove() before next()" }
        delegate.remove()
      }
    }
  }

  @Synchronized fun writeAbortCount(): Int = writeAbortCount

  @Synchronized fun writeSuccessCount(): Int = writeSuccessCount

  @Throws(IOException::class)
  fun size(): Long = cache.size()

  /** Max size of the cache (in bytes). */
  fun maxSize(): Long = cache.maxSize

  @Throws(IOException::class)
  override fun flush() {
    cache.flush()
  }

  @Throws(IOException::class)
  override fun close() {
    cache.close()
  }

  @get:JvmName("directory") val directory: File
    get() = cache.directory

  @JvmName("-deprecated_directory")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "directory"),
      level = DeprecationLevel.ERROR)
  fun directory(): File = cache.directory

  @Synchronized internal fun trackResponse(cacheStrategy: CacheStrategy) {
    requestCount++

    if (cacheStrategy.networkRequest != null) {
      // If this is a conditional request, we'll increment hitCount if/when it hits.
      networkCount++
    } else if (cacheStrategy.cacheResponse != null) {
      // This response uses the cache and not the network. That's a cache hit.
      hitCount++
    }
  }

  @Synchronized internal fun trackConditionalCacheHit() {
    hitCount++
  }

  @Synchronized fun networkCount(): Int = networkCount

  @Synchronized fun hitCount(): Int = hitCount

  @Synchronized fun requestCount(): Int = requestCount

  private inner class RealCacheRequest(
    private val editor: DiskLruCache.Editor
  ) : CacheRequest {
    private val cacheOut: Sink = editor.newSink(ENTRY_BODY)
    private val body: Sink
    var done = false

    init {
      this.body = object : ForwardingSink(cacheOut) {
        @Throws(IOException::class)
        override fun close() {
          synchronized(this@Cache) {
            if (done) return
            done = true
            writeSuccessCount++
          }
          super.close()
          editor.commit()
        }
      }
    }

    override fun abort() {
      synchronized(this@Cache) {
        if (done) return
        done = true
        writeAbortCount++
      }
      cacheOut.closeQuietly()
      try {
        editor.abort()
      } catch (_: IOException) {
      }
    }

    override fun body(): Sink = body
  }

  private class Entry {
    private val url: HttpUrl  // 请求的URL
    private val varyHeaders: Headers  // 可变的请求头信息
    private val requestMethod: String  // 请求方法（GET、POST等）
    private val protocol: Protocol  // 使用的协议（HTTP/1.1、HTTP/2等）
    private val code: Int  // HTTP响应状态码
    private val message: String  // 响应消息
    private val responseHeaders: Headers  // 响应头信息
    private val handshake: Handshake?  // 安全握手信息（如果使用HTTPS）
    private val sentRequestMillis: Long  // 发送请求的时间戳
    private val receivedResponseMillis: Long  // 收到响应的时间戳

    private val isHttps: Boolean get() = url.scheme == "https"  // 是否使用HTTPS协议

  /**
     * Reads an entry from an input stream. A typical entry looks like this:
     *
     * ```
     * http://google.com/foo
     * GET
     * 2
     * Accept-Language: fr-CA
     * Accept-Charset: UTF-8
     * HTTP/1.1 200 OK
     * 3
     * Content-Type: image/png
     * Content-Length: 100
     * Cache-Control: max-age=600
     * ```
     *
     * A typical HTTPS file looks like this:
     *
     * ```
     * https://google.com/foo
     * GET
     * 2
     * Accept-Language: fr-CA
     * Accept-Charset: UTF-8
     * HTTP/1.1 200 OK
     * 3
     * Content-Type: image/png
     * Content-Length: 100
     * Cache-Control: max-age=600
     *
     * AES_256_WITH_MD5
     * 2
     * base64-encoded peerCertificate[0]
     * base64-encoded peerCertificate[1]
     * -1
     * TLSv1.2
     * ```
     *
     * The file is newline separated. The first two lines are the URL and the request method. Next
     * is the number of HTTP Vary request header lines, followed by those lines.
     *
     * Next is the response status line, followed by the number of HTTP response header lines,
     * followed by those lines.
     *
     * HTTPS responses also contain SSL session information. This begins with a blank line, and then
     * a line containing the cipher suite. Next is the length of the peer certificate chain. These
     * certificates are base64-encoded and appear each on their own line. The next line contains the
     * length of the local certificate chain. These certificates are also base64-encoded and appear
     * each on their own line. A length of -1 is used to encode a null array. The last line is
     * optional. If present, it contains the TLS version.
     */
  /*
  这段代码主要是用于解析缓存数据，提取URL、请求方法、请求头、响应头等信息，
  同时也包括了对HTTPS连接的握手信息的解析。
  如果在解析过程中出现问题，会抛出IOException。在HTTPS连接中，还会解析TLS握手信息。
   */
  @Throws(IOException::class)
  constructor(rawSource: Source) {
    rawSource.use {
      // 创建一个缓冲的Source对象
      val source = rawSource.buffer()

      // 从source中读取第一行，该行应该包含URL信息
      val urlLine = source.readUtf8LineStrict()
      // 从缓存中获取url
      url = urlLine.toHttpUrlOrNull() ?: throw IOException("Cache corruption for $urlLine").also {
        // 记录日志，标识缓存损坏
        Platform.get().log("cache corruption", Platform.WARN, it)
      }

      // 从source中读取HTTP请求方法
      requestMethod = source.readUtf8LineStrict()

      // 创建一个Headers.Builder来构建可变请求头
      val varyHeadersBuilder = Headers.Builder()
      // 读取请求头行数
      val varyRequestHeaderLineCount = readInt(source)
      // 读取每一行请求头并添加到varyHeadersBuilder中
      for (i in 0 until varyRequestHeaderLineCount) {
        varyHeadersBuilder.addLenient(source.readUtf8LineStrict())
      }
      // 构建不可变请求头Headers对象
      varyHeaders = varyHeadersBuilder.build()

      // 从source中读取状态行并解析
      val statusLine = StatusLine.parse(source.readUtf8LineStrict())
      // 分别提取协议、状态码和状态消息
      protocol = statusLine.protocol
      code = statusLine.code
      message = statusLine.message

      // 创建一个Headers.Builder来构建可变响应头
      val responseHeadersBuilder = Headers.Builder()
      // 读取响应头行数
      val responseHeaderLineCount = readInt(source)
      // 读取每一行响应头并添加到responseHeadersBuilder中
      for (i in 0 until responseHeaderLineCount) {
        responseHeadersBuilder.addLenient(source.readUtf8LineStrict())
      }

      // 从响应头中提取发送请求的时间和接收响应的时间
      val sendRequestMillisString = responseHeadersBuilder[SENT_MILLIS]
      val receivedResponseMillisString = responseHeadersBuilder[RECEIVED_MILLIS]
      // 从响应头中移除这两个字段
      responseHeadersBuilder.removeAll(SENT_MILLIS)
      responseHeadersBuilder.removeAll(RECEIVED_MILLIS)
      // 将时间字符串转换为长整型，如果为空则默认为0
      sentRequestMillis = sendRequestMillisString?.toLong() ?: 0L
      receivedResponseMillis = receivedResponseMillisString?.toLong() ?: 0L
      // 构建不可变响应头Headers对象
      responseHeaders = responseHeadersBuilder.build()

      // 如果是HTTPS连接
      if (isHttps) {
        // 读取一个空行，如果不为空则抛出IOException
        val blank = source.readUtf8LineStrict()
        if (blank.isNotEmpty()) {
          throw IOException("expected \"\" but was \"$blank\"")
        }

        // 从source中读取密码套件字符串并转换为CipherSuite对象
        val cipherSuiteString = source.readUtf8LineStrict()
        val cipherSuite = CipherSuite.forJavaName(cipherSuiteString)

        // 读取对等证书列表和本地证书列表
        val peerCertificates = readCertificateList(source)
        val localCertificates = readCertificateList(source)

        // 从source中读取TLS版本字符串，如果不存在则默认为TLS 1.0
        val tlsVersion = if (!source.exhausted()) {
          TlsVersion.forJavaName(source.readUtf8LineStrict())
        } else {
          TlsVersion.TLS_1_0
        }
        // 构建握手信息对象
        handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates)
      } else {
        // 如果不是HTTPS连接，则握手信息为空
        handshake = null
      }
    }
  }


    //插入缓存或者更新缓存的时候使用到的
    constructor(response: Response) {
      this.url = response.request.url
      this.varyHeaders = response.varyHeaders()
      this.requestMethod = response.request.method
      this.protocol = response.protocol
      this.code = response.code
      this.message = response.message
      this.responseHeaders = response.headers
      this.handshake = response.handshake
      this.sentRequestMillis = response.sentRequestAtMillis
      this.receivedResponseMillis = response.receivedResponseAtMillis
    }

    /**
     * 这段代码的作用是将缓存信息写入到一个 DiskLruCache.Editor 中，包括URL、请求方法、请求头、响应头、时间信息等。
     * 如果是HTTPS连接，还会包括握手信息的写入。
     */
    @Throws(IOException::class)
    fun writeTo(editor: DiskLruCache.Editor) {
      // 获取用于写入元数据的Sink
      editor.newSink(ENTRY_METADATA).buffer().use { sink ->
        // 写入URL信息并添加换行
        sink.writeUtf8(url.toString()).writeByte('\n'.toInt())

        // 写入HTTP请求方法并添加换行
        sink.writeUtf8(requestMethod).writeByte('\n'.toInt())

        // 写入Vary请求头的数量并添加换行
        sink.writeDecimalLong(varyHeaders.size.toLong()).writeByte('\n'.toInt())

        // 循环写入每个Vary请求头的名称和值，并添加换行
        for (i in 0 until varyHeaders.size) {
          sink.writeUtf8(varyHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(varyHeaders.value(i))
            .writeByte('\n'.toInt())
        }

        // 写入状态行（协议、状态码和状态消息）并添加换行
        sink.writeUtf8(StatusLine(protocol, code, message).toString()).writeByte('\n'.toInt())

        // 写入响应头的数量（加上两个时间字段）并添加换行
        sink.writeDecimalLong((responseHeaders.size + 2).toLong()).writeByte('\n'.toInt())

        // 循环写入每个响应头的名称和值，并添加换行
        for (i in 0 until responseHeaders.size) {
          sink.writeUtf8(responseHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(responseHeaders.value(i))
            .writeByte('\n'.toInt())
        }

        // 写入发送请求的时间字段（SENT_MILLIS）和值，并添加换行
        sink.writeUtf8(SENT_MILLIS)
          .writeUtf8(": ")
          .writeDecimalLong(sentRequestMillis)
          .writeByte('\n'.toInt())

        // 写入接收响应的时间字段（RECEIVED_MILLIS）和值，并添加换行
        sink.writeUtf8(RECEIVED_MILLIS)
          .writeUtf8(": ")
          .writeDecimalLong(receivedResponseMillis)
          .writeByte('\n'.toInt())

        // 如果是HTTPS连接
        if (isHttps) {
          // 添加一个额外的空行
          sink.writeByte('\n'.toInt())

          // 写入密码套件（CipherSuite）的名称，并添加换行
          sink.writeUtf8(handshake!!.cipherSuite.javaName).writeByte('\n'.toInt())

          // 写入对等证书列表和本地证书列表
          writeCertList(sink, handshake.peerCertificates)
          writeCertList(sink, handshake.localCertificates)

          // 写入TLS版本的名称，并添加换行
          sink.writeUtf8(handshake.tlsVersion.javaName).writeByte('\n'.toInt())
        }
      }
    }


    @Throws(IOException::class)
    private fun readCertificateList(source: BufferedSource): List<Certificate> {
      val length = readInt(source)
      if (length == -1) return emptyList() // OkHttp v1.2 used -1 to indicate null.

      try {
        val certificateFactory = CertificateFactory.getInstance("X.509")
        val result = ArrayList<Certificate>(length)
        for (i in 0 until length) {
          val line = source.readUtf8LineStrict()
          val bytes = Buffer()
          val certificateBytes = line.decodeBase64() ?: throw IOException("Corrupt certificate in cache entry")
          bytes.write(certificateBytes)
          result.add(certificateFactory.generateCertificate(bytes.inputStream()))
        }
        return result
      } catch (e: CertificateException) {
        throw IOException(e.message)
      }
    }

    @Throws(IOException::class)
    private fun writeCertList(sink: BufferedSink, certificates: List<Certificate>) {
      try {
        sink.writeDecimalLong(certificates.size.toLong()).writeByte('\n'.toInt())
        for (element in certificates) {
          val bytes = element.encoded
          val line = bytes.toByteString().base64()
          sink.writeUtf8(line).writeByte('\n'.toInt())
        }
      } catch (e: CertificateEncodingException) {
        throw IOException(e.message)
      }
    }

    fun matches(request: Request, response: Response): Boolean {
      return url == request.url &&
          requestMethod == request.method &&
          varyMatches(response, varyHeaders, request)
    }

    fun response(snapshot: DiskLruCache.Snapshot): Response {
      val contentType = responseHeaders["Content-Type"]
      val contentLength = responseHeaders["Content-Length"]
      val cacheRequest = Request.Builder()
          .url(url)
          .method(requestMethod, null)
          .headers(varyHeaders)
          .build()
      return Response.Builder()
          .request(cacheRequest)
          .protocol(protocol)
          .code(code)
          .message(message)
          .headers(responseHeaders)
          .body(CacheResponseBody(snapshot, contentType, contentLength))
          .handshake(handshake)
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(receivedResponseMillis)
          .build()
    }

    companion object {
      /** Synthetic response header: the local time when the request was sent. */
      private val SENT_MILLIS = "${Platform.get().getPrefix()}-Sent-Millis"

      /** Synthetic response header: the local time when the response was received. */
      private val RECEIVED_MILLIS = "${Platform.get().getPrefix()}-Received-Millis"
    }
  }

  private class CacheResponseBody(
    val snapshot: DiskLruCache.Snapshot,
    private val contentType: String?,
    private val contentLength: String?
  ) : ResponseBody() {
    private val bodySource: BufferedSource

    init {
      val source = snapshot.getSource(ENTRY_BODY)
      bodySource = object : ForwardingSource(source) {
        @Throws(IOException::class)
        override fun close() {
          snapshot.close()
          super.close()
        }
      }.buffer()
    }

    override fun contentType(): MediaType? = contentType?.toMediaTypeOrNull()

    override fun contentLength(): Long = contentLength?.toLongOrDefault(-1L) ?: -1L

    override fun source(): BufferedSource = bodySource
  }

  companion object {
    private const val VERSION = 201105
    private const val ENTRY_METADATA = 0
    private const val ENTRY_BODY = 1
    private const val ENTRY_COUNT = 2

    @JvmStatic
    fun key(url: HttpUrl): String = url.toString().encodeUtf8().md5().hex()

    @Throws(IOException::class)
    internal fun readInt(source: BufferedSource): Int {
      try {
        val result = source.readDecimalLong()
        val line = source.readUtf8LineStrict()
        if (result < 0L || result > Integer.MAX_VALUE || line.isNotEmpty()) {
          throw IOException("expected an int but was \"$result$line\"")
        }
        return result.toInt()
      } catch (e: NumberFormatException) {
        throw IOException(e.message)
      }
    }

    /**
     * Returns true if none of the Vary headers have changed between [cachedRequest] and
     * [newRequest].
     */
    fun varyMatches(
      cachedResponse: Response,
      cachedRequest: Headers,
      newRequest: Request
    ): Boolean {
      return cachedResponse.headers.varyFields().none {
        cachedRequest.values(it) != newRequest.headers(it)
      }
    }

    /** Returns true if a Vary header contains an asterisk. Such responses cannot be cached. */
    fun Response.hasVaryAll() = "*" in headers.varyFields()

    /**
     * Returns the names of the request headers that need to be checked for equality when caching.
     */
    private fun Headers.varyFields(): Set<String> {
      var result: MutableSet<String>? = null
      for (i in 0 until size) {
        if (!"Vary".equals(name(i), ignoreCase = true)) {
          continue
        }

        val value = value(i)
        if (result == null) {
          result = TreeSet(String.CASE_INSENSITIVE_ORDER)
        }
        for (varyField in value.split(',')) {
          result.add(varyField.trim())
        }
      }
      return result ?: emptySet()
    }

    /**
     * Returns the subset of the headers in this's request that impact the content of this's body.
     */
    fun Response.varyHeaders(): Headers {
      // Use the request headers sent over the network, since that's what the response varies on.
      // Otherwise OkHttp-supplied headers like "Accept-Encoding: gzip" may be lost.
      val requestHeaders = networkResponse!!.request.headers
      val responseHeaders = headers
      return varyHeaders(requestHeaders, responseHeaders)
    }

    /**
     * Returns the subset of the headers in [requestHeaders] that impact the content of the
     * response's body.
     */
    private fun varyHeaders(requestHeaders: Headers, responseHeaders: Headers): Headers {
      val varyFields = responseHeaders.varyFields()
      if (varyFields.isEmpty()) return EMPTY_HEADERS

      val result = Headers.Builder()
      for (i in 0 until requestHeaders.size) {
        val fieldName = requestHeaders.name(i)
        if (fieldName in varyFields) {
          result.add(fieldName, requestHeaders.value(i))
        }
      }
      return result.build()
    }
  }
}
